package com.example.shiwu.util;

import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.primitives.Ints;
import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.primitives.Longs;
import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.primitives.Shorts;
import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;


import org.apache.commons.lang3.ArrayUtils;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CoderUtil {
    public static void main(String[] args) throws Exception {
        String pattern = "h2,h4,s,h4,i";
        System.out.println(pattern);
        String value = "621E,0000FEB1,29,FFFFFFFF,21";
        System.out.println(value);
        String encode = HexBin.encode(encode(pattern, value, false));
        System.out.println(encode);
        System.out.println(String.join(",",decode(pattern,HexBin.decode(encode),false)));

    }

    public static List<String> getSrcCode(byte[] bytes, String pattern) throws Exception {
        pattern = pattern.replace("i", "i4")
                .replace("s", "s2")
                .replace("f", "f4")
                .replace("l", "l8");
        boolean hasBit = pattern.contains("b");
        byte[] bits = BitUtils.bytesToBits(bytes);
        ByteBuffer bytesBuf = ByteBuffer.wrap(bytes);
        bytesBuf.position(0);
        ByteBuffer bitsBuf = ByteBuffer.wrap(bits);
        bitsBuf.position(0);
        List<String> srcCodeList = new ArrayList<>();
        String[] pa = pattern.split(",");
        for (String s : pa) {
            String type = s.substring(0, 1);
            int len = Integer.parseInt(s.substring(1));
            byte[] bytesTemp;
            len = hasBit && !type.equals("b") ? len * 8 : len;
            bytesTemp = new byte[len];
            if (hasBit) {
                bitsBuf.get(bytesTemp, 0, len);
            } else {
                bytesBuf.get(bytesTemp, 0, len);
            }
            switch (type) {
                case "i":
                case "s":
                case "l":
                case "f":
                case "h":
                    srcCodeList.add(HexBin.encode(hasBit ? BitUtils.bitsToBytes(Arrays.toString(bytesTemp)) : bytesTemp));
                    break;
                case "b":
                    StringBuilder sb = new StringBuilder();
                    for (byte b : bytesTemp) sb.append(b);
                    srcCodeList.add(sb.toString());
                    break;
                default:
                    throw new Exception("not support this type : " + type);
            }
        }
        return srcCodeList;
    }


    /**
     * @param pattern 字节数组的属性格式
     *                例如：i,s,l,h4,b8，其中h和b后面需要指定字节长度
     *                i：表示4个字节的int
     *                s：表示2个字节的short，
     *                l：表示8个字节的long,
     *                h4：表示4个字节的16进制，
     *                b8：表示8个比特
     * @param value   各属性的标示值，用“，”分割。例如："2,3,4,A1B1C1D1,00001111"
     * @return 组合后的字节数组
     * @throws Exception 不支持类型
     */
    public static byte[] encode(String pattern, String value, boolean isLittleEndian) throws Exception {
        pattern = pattern.replace("i", "i4")
                .replace("s", "s2")
                .replace("f", "f4")
                .replace("l", "l8");
        String[] parArr = pattern.split(",");
        String[] vaArr = value.split(",");
        boolean hasBit = pattern.contains("b");
        List<byte[]> bytesList = new ArrayList<>();
        for (int i = 0; i < parArr.length; i++) {
            String type = parArr[i].substring(0, 1);
            String num = parArr[i].substring(1);
            int len = Integer.parseInt(num);
            String strValue = vaArr[i];
            byte[] bytes;
            switch (type) {
                case "i":
                    bytes = Ints.toByteArray(Integer.parseInt(strValue));
                    if (isLittleEndian) ArrayUtils.reverse(bytes);
                    bytesList.add(hasBit ? BitUtils.bytesToBits(bytes) : bytes);
                    break;
                case "s":
                    bytes = Shorts.toByteArray(Short.parseShort(strValue));
                    if (isLittleEndian) ArrayUtils.reverse(bytes);
                    bytesList.add(hasBit ? BitUtils.bytesToBits(bytes) : bytes);
                    break;
                case "f":
                    float[] floats = {Float.parseFloat(strValue)};
                    bytes = ByteUtils.floatArrToByteArr(floats);
                    if (isLittleEndian) ArrayUtils.reverse(bytes);
                    bytesList.add(hasBit ? BitUtils.bytesToBits(bytes) : bytes);
                    break;
                case "l":
                    bytes = Longs.toByteArray(Long.parseLong(strValue));
                    if (isLittleEndian) ArrayUtils.reverse(bytes);
                    bytesList.add(hasBit ? BitUtils.bytesToBits(bytes) : bytes);
                    break;
                case "h":
                    bytes = HexBin.decode(strValue);
                    byte[] bytes1 = new byte[len];
                    System.arraycopy(bytes, 0, bytes1, len - bytes.length, bytes.length);
                    if (isLittleEndian) ArrayUtils.reverse(bytes1);
                    bytesList.add(hasBit ? BitUtils.bytesToBits(bytes1) : bytes1);
                    break;
                case "b":
                    bytes = new byte[strValue.length()];
                    for (int k = 0; k < strValue.length(); k++) {
                        bytes[k] = Byte.parseByte(String.valueOf(strValue.charAt(k)));
                    }
                    bytes1 = new byte[len];
                    System.arraycopy(bytes, 0, bytes1, len - bytes.length, bytes.length);
                    bytesList.add(bytes);
                    break;
                default:
                    throw new Exception("not support this type : " + type);
            }
        }
        int len = 0;
        for (byte[] bytes : bytesList) {
            len += bytes.length;
        }
        ByteBuffer buffer = ByteBuffer.allocate(len);
        for (byte[] bytes : bytesList) {
            buffer.put(bytes);
        }
        return hasBit ? BitUtils.bitsToBytes(Arrays.toString(buffer.array())) : buffer.array();
    }

    public static String[] decode(String pattern, byte[] bytes, boolean isLittleEndian) throws Exception {
        pattern = pattern.replace("i", "i4")
                .replace("s", "s2")
                .replace("f", "f4")
                .replace("l", "l8");
        boolean hasBit = pattern.contains("b");
        byte[] bits = BitUtils.bytesToBits(bytes);
        ByteBuffer bytesBuf = ByteBuffer.wrap(bytes);
        bytesBuf.position(0);
        ByteBuffer bitsBuf = ByteBuffer.wrap(bits);
        bitsBuf.position(0);
        String[] parArr = pattern.split(",");
        String[] resultArr = new String[parArr.length];
        for (int i = 0; i < parArr.length; i++) {
            String s = parArr[i];
            String type = s.substring(0, 1);
            int len = Integer.parseInt(s.substring(1));
            byte[] bytesTemp;
            len = hasBit && !type.equals("b") ? len * 8 : len;
            bytesTemp = new byte[len];
            if (hasBit) {
                bitsBuf.get(bytesTemp, 0, len);
            } else {
                bytesBuf.get(bytesTemp, 0, len);
            }
            byte[] tmp = hasBit ? BitUtils.bitsToBytes(Arrays.toString(bytesTemp)) : bytesTemp;
            if (isLittleEndian){
                ArrayUtils.reverse(tmp);
            }
            switch (type) {
                case "i":
                    resultArr[i] = String.valueOf(Ints.fromByteArray(tmp));
                    break;
                case "s":
                    resultArr[i] = String.valueOf(Shorts.fromByteArray(tmp));
                    break;
                case "l":
                    resultArr[i] = String.valueOf(Longs.fromByteArray(tmp));
                    break;
                case "f":
                    resultArr[i] = String.valueOf(ByteUtils.byteArrToFloatArr(tmp)[0]);
                    break;
                case "h":
                    resultArr[i] = HexBin.encode(tmp);
                    break;
                case "b":
                    StringBuilder sb = new StringBuilder();
                    for (byte b : bytesTemp) sb.append(b);
                    resultArr[i] = sb.toString();
                    break;
                default:
                    throw new Exception("not support this type : " + type);
            }
        }
        return resultArr;
    }
}
